/**
 * 雪花算法构成
 * 符号位（1bit）- 时间戳相对值（41bit）- 数据中心标志（5bit）- 机器标志（5bit）- 递增序号（12bit）
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
 */
class Snowflake {
    // 机器ID bit数
    static get WorkIdBits () { return 5 }
    // 最大机器ID
    static get MaxWorkId () { return -1 ^ (-1 << Snowflake.WorkIdBits) }
    // 数据中心ID bit数
    static get DataCenterIdBits () { return 5 }
    // 做大数据中心ID
    static get MaxDataCenterId () { return -1 ^ (-1 << Snowflake.DataCenterIdBits) }
    // 序列号bit数
    static get SequenceBits () { return 12 }
    // 最大序列号
    static get MaxSequence () { return ~(-1 << Snowflake.SequenceBits) }
    // 上次ID时间戳
    #lastTimestamp = -1
    // 序列号
    #sequence = 0

    /**
     * 构造函数
     * @param {Number} dataCenterId 数据中心ID
     * @param {Number} workId 机器ID
     * @param {Boolean} randomStart  是否使用随机数（0-100之间）作为开始序号（避免数据倾斜）
     * @param {Number} twepoch 开始时间
     */
    constructor(dataCenterId, workId, randomStart, twepoch) {
        this.dataCenterId = dataCenterId || 0
        this.workId = workId || 0
        this.randomStart = randomStart || false
        this.twepoch = twepoch || 1288834974657
        if (this.dataCenterId < 0 || this.dataCenterId > Snowflake.MaxDataCenterId) {
            throw Error('数据中心ID不符合要求')
        }
        if (this.workId < 0 || this.workId > Snowflake.MaxWorkId) {
            throw Error('工作ID不符合要求')
        }
    }

    /**
     * 获取下一个ID
     * @param {*} num 需要生成的ID数量 默认一个
     * @param {*} singleReturnStr 一个的时候是否之间返回字符串类型（多个返回数组类型）
     * @returns id
     */
    nextId (num = 1, singleReturnStr = true) {
        num = num < 1 ? 1 : num
        const currentTime = new Date().getTime()
        if (singleReturnStr && num === 1) {
            return this.#getNextId(currentTime)
        }
        const ids = []
        for (let i = 0; i < num; i++) {
            ids.push(this.#getNextId(currentTime))
        }
        return ids
    }

    /**
     * 解析雪花ID生成时间
     * @param {*} snowflakeId 雪花ID
     * @returns 生成时间时间戳
     */
    parseIdGenerateTime (snowflakeId) {
        // 雪花id转为二进制
        const idBinStr = this.#snowflakeIdParse(snowflakeId)
        // 获取时间结束位置的索引
        const endIndex = idBinStr.length - Snowflake.WorkIdBits - Snowflake.DataCenterIdBits - Snowflake.SequenceBits
        // 把时间二进制字段转为数字 并加上twepoch时间 获取生成时间时间戳
        const generateTimeBin = idBinStr.substring(0, endIndex)
        return parseInt(generateTimeBin, 2) + this.twepoch
    }

    parseDataCenterId (snowflakeId) {
        // 雪花id转为二进制
        const idBinStr = this.#snowflakeIdParse(snowflakeId)
        // 数据中心开始索引
        const startIndex = idBinStr.length - Snowflake.DataCenterIdBits - Snowflake.WorkIdBits - Snowflake.SequenceBits
        // 数据中心结束索引
        const endIndex = idBinStr.length - Snowflake.WorkIdBits - Snowflake.SequenceBits
        // 获取数据中心二进制数据
        const dataCenterIdBin = idBinStr.substring(startIndex, endIndex)
        return parseInt(dataCenterIdBin, 2)
    }

    parseWorkerId (snowflakeId) {
        // 雪花id转为二进制
        const idBinStr = this.#snowflakeIdParse(snowflakeId)
        // 机器ID开始索引
        const startIndex = idBinStr.length - Snowflake.WorkIdBits - Snowflake.SequenceBits
        // 机器ID结束索引
        const endIndex = idBinStr.length - Snowflake.SequenceBits
        // 获取数据中心二进制数据
        const workerIdBin = idBinStr.substring(startIndex, endIndex)
        return parseInt(workerIdBin, 2)
    }

    /**
     * 解析雪花ID
     * @param {*} snowflakeId 雪花ID
     * @returns 二进制类型雪花ID
     */
    #snowflakeIdParse (snowflakeId) {
        if (!snowflakeId) {
            throw Error('id不合法')
        }
        return BigInt(snowflakeId).toString(2)
    }

    /**
     * 获取下一个ID
     * @param {Number} currentTime  当前时间
     * @returns 下一个ID
     */
    #getNextId (currentTime) {
        if (currentTime < this.#lastTimestamp) {
            // 时钟回拨 将当前时间设置为最后一毫秒时间
            currentTime = this.#lastTimestamp
        }
        if (currentTime === this.#lastTimestamp) {
            // 说明是统一毫秒内生成
            const nextSequence = (this.#sequence + 1) & Snowflake.MaxSequence
            if (nextSequence === 0) {
                // 说明超了 使用下一毫秒 currentTime 也要设置为当前时间 因为后面会使用 currentTime 去修改 lastTimestamp 的值
                currentTime = this.#lastTimestamp = this.#lastTimestamp + 1
                // 从0开始 忽略randomStart参数 id会用完那就不存在数据倾斜问题
                this.#sequence = 0
            } else {
                // 正常生成
                this.#sequence = nextSequence
            }
        } else {
            // 新的毫秒内
            if (this.randomStart) {
                this.#sequence = ~~(Math.random() * 101);
            } else {
                this.#sequence = 0
            }
        }
        this.#lastTimestamp = currentTime
        // 因为直接使用位运算会超过Number精度会出问题 所以先把所需参数转换为字符串 在使用BigInt解析回来
        // 需要解析的二进制字符串 把各部分进行组合 雪花算法第一位固定为0
        let binIdStr = '0b0'
        // 时间部分转二进制
        const timeBinStr = (this.#lastTimestamp - this.twepoch).toString(2)
        binIdStr = this.#buildPart(binIdStr, timeBinStr, 41)
        // 数据中心ID转二进制
        const dataCenterBinStr = (this.dataCenterId).toString(2)
        binIdStr = this.#buildPart(binIdStr, dataCenterBinStr, 5)
        // 机器ID转二进制
        const workBinStr = (this.workId).toString(2)
        binIdStr = this.#buildPart(binIdStr, workBinStr, 5)
        // 序列号转二进制
        const sequencebinStr = (this.#sequence).toString(2)
        binIdStr = this.#buildPart(binIdStr, sequencebinStr, 12)
        // 使用BigInt解析二进制字符串 并转为字符串返回
        return BigInt(binIdStr).toString()
    }

    /**
     * 构造所需的部分参数
     * @param {String} originStr 原始字符串 在这个字符串基础上添加后续字符串
     * @param {String} binStr 需要添加的二进制字符串
     * @param {Number} length 当前部分长度
     * @returns 构建后的字符串
     */
    #buildPart (originStr, binStr, length) {
        if (binStr.length < length) {
            const _n = length - binStr.length
            // 不足部分补0
            for (let i = 0; i < _n; i++) {
                originStr += '0'
            }
        }
        originStr += binStr
        return originStr
    }
}